package com.hyt.it.ogt.kq.service.admission.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.handler.AbstractRowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.base.BaseServiceImpl;
import com.hyt.core.util.RemoteUtil;
import com.hyt.core.util.UUIDUtils;
import com.hyt.exception.exception.BusinessLogicException;
import com.hyt.exception.exception.BusinessRequestException;
import com.hyt.it.ogt.cj.service.IProjectCollectInfoOptionService;
import com.hyt.it.ogt.cj.service.IProjectCollectInfoService;
import com.hyt.it.ogt.cj.service.IProjectImageStandardService;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.gov.enums.GenerateStatusEnum;
import com.hyt.it.ogt.kq.common.gov.enums.IdentityTypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.admission.AdmissionSexEnum;
import com.hyt.it.ogt.kq.common.gov.enums.admission.TemplateTypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.admission.TicketDataSourceEnum;
import com.hyt.it.ogt.kq.common.gov.enums.admission.TicketSubjectSettingEnum;
import com.hyt.it.ogt.kq.service.admission.*;
import com.hyt.it.ogt.kq.service.gov.feign.cloudefile.CloudFileClient;
import com.hyt.it.ogt.kq.service.gov.feign.ks.KsFallbackFactoryClient;
import com.hyt.it.ogt.kq.service.gov.feign.ks.model.KwExamVO;
import com.hyt.it.ogt.kq.service.gov.mapper.admission.AdmissionTicketInfoMapper;
import com.hyt.it.ogt.kq.service.gov.model.dto.admission.ExportExamineesInfoDTO;
import com.hyt.it.ogt.kq.service.gov.model.dto.admission.PageByCondition;
import com.hyt.it.ogt.kq.service.gov.model.entity.Task;
import com.hyt.it.ogt.kq.service.gov.model.param.admission.AddOrUpdateAdmissionTicketInfoParam;
import com.hyt.it.ogt.kq.service.gov.model.param.admission.ExamineeInfoExportParam;
import com.hyt.it.ogt.kq.service.gov.model.param.admission.UpdateAdmissionTicketInfoParam;
import com.hyt.it.ogt.kq.service.gov.model.vo.admission.AdmissionDataSourceVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.admission.AdmissionKsNameVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.admission.AdmissionTicketInfoVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.admission.ExamineeInfoPageVO;
import com.hyt.it.ogt.kq.service.gov.service.ITaskService;
import com.hyt.it.ogt.kq.service.gov.service.obs.CloudObsService;
import com.hyt.it.ogt.kq.service.model.entity.admission.AdmissionTicketInfo;
import com.hyt.it.ogt.kq.service.model.entity.admission.ExamineeInfo;
import com.hyt.it.ogt.kq.service.model.entity.admission.ExamineeTimes;
import com.hyt.loginfo.model.Organization;
import com.hyt.loginfo.model.User;
import com.hyt.loginfo.service.IBaseLoginService;
import com.hyt.model.cloudefile.apiparams.FileRecodeParam;
import com.hyt.model.cloudefile.apiparams.PushTaskParam;
import com.hyt.model.cloudefile.entity.CloudeFileProjectRecord;
import com.hyt.model.cloudefile.enums.*;
import com.hyt.progress.ProgressHolder;
import com.hyt.progress.constants.ProgressConstant;
import com.hyt.progress.service.ProgressManager;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Row;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.File;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.hyt.it.ogt.kq.common.gov.constant.ResponseCode.*;
import static com.hyt.it.ogt.kq.common.gov.constant.TaskExamTypeEnum.OFFLINE;
import static com.hyt.it.ogt.kq.common.gov.enums.admission.TicketDataSourceEnum.NOT_ROOM;
import static com.hyt.it.ogt.kq.common.gov.enums.admission.TicketDataSourceEnum.V_ROOM;
import static com.hyt.it.ogt.kq.common.gov.enums.admission.TicketSubjectSettingEnum.MORE;

/**
 * <p>
 * 准考证定义表 服务实现类
 * </p>
 *
 * @author yanghuankun@talkweb.com.cn
 * @since 2023-01-10
 */
@Service
@Slf4j
public class AdmissionTicketInfoServiceImpl extends BaseServiceImpl<AdmissionTicketInfoMapper, AdmissionTicketInfo> implements IAdmissionTicketInfoService {

    @Resource
    IBaseLoginService loginService;
    @Resource
    CloudObsService cloudObsService;
    @Resource
    IExamineeInfoService iExamineeInfoService;
    @Resource
    private IAdmissionTicketTemplateService iAdmissionTicketTemplateService;
    @Resource
    private KsFallbackFactoryClient ksFallbackFactoryClient;
    @Resource
    private ITaskService iTaskService;
    @Resource
    private IExamineeTimesService iExamineeTimesService;
    @Resource
    private IAdmissionTicketTemplateVariableService iAdmissionTicketTemplateVariableService;
    @Resource
    CloudFileClient cloudFileClient;
    @Resource
    private IProjectCollectInfoService iProjectCollectInfoService;
    @Resource
    private IProjectCollectInfoOptionService iProjectCollectInfoOptionService;
    @Resource
    private IProjectImageStandardService iProjectImageStandardService;
    @Resource
    IAdmissionTicketInfoService iAdmissionTicketInfoService;

    /**
     * 新增准考证任务
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @param currentUser
     * @param deptId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String addOrUpdate(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam, User currentUser, String deptId) {
        String admissionTicketInfoId = addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId();
        if (StrUtil.isBlank(admissionTicketInfoId)) {
            ProgressManager.updateProgressCurrent(5L, "开始创建");
            // 新增准考证任务
            // 参数校验
            validationAddAdmissionTicketInfoParam(addOrUpdateAdmissionTicketInfoParam, currentUser, deptId);

            // 创建准考证模板
            String templateId = addOrUpdateAdmissionTicketInfoParam.getTemplateType() + "";
            boolean isCustomTemplate = Objects.equals(TemplateTypeEnum.CUSTOM.getCode(), addOrUpdateAdmissionTicketInfoParam.getTemplateType());
            if (isCustomTemplate) {
                // 创建自定义模板
                templateId = iAdmissionTicketTemplateService.createCustomTemplate(currentUser, deptId).getId();
            }

            // 创建准考证任务
            AdmissionTicketInfo admissionTicketInfo = createAdmissionTicketInfo(addOrUpdateAdmissionTicketInfoParam, currentUser, deptId, templateId);
            log.info("[创建或者编辑准考证]创建准考证任务，创建准考证任务完成: {}", admissionTicketInfo);

            // 初始化标准模板的变量配置
            if (!isCustomTemplate) {
                iAdmissionTicketTemplateVariableService.initStandardTemplateVariable(addOrUpdateAdmissionTicketInfoParam, admissionTicketInfo.getId(), currentUser, deptId);
            } else {
                // 初始化自定义模板变量
                iAdmissionTicketTemplateVariableService.initCustomTemplateVariable(templateId, admissionTicketInfo.getId(), currentUser, deptId);
            }

            // 更新准考证任务id到自定义模板中
            if (isCustomTemplate) {
                iAdmissionTicketTemplateService.updateAdmissionTicketInfoId(admissionTicketInfo.getId(), templateId, currentUser);
            }

            // 同步数据
            syncData(addOrUpdateAdmissionTicketInfoParam, currentUser, admissionTicketInfo.getId(), deptId);
            admissionTicketInfoId = admissionTicketInfo.getId();
        } else {
            ProgressManager.updateProgressCurrent(5L, "开始更新");

            // 编辑准考证任务
            AdmissionTicketInfo dbAdmissionTicketInfo = getByAdmissionIdOrThrow(addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId());

            // 校验编辑参数
            validationUpdateAdmissionTicketInfoParam(addOrUpdateAdmissionTicketInfoParam, currentUser, deptId, dbAdmissionTicketInfo);

            // 更新准考证未创建完成
            updateCreateStatus(admissionTicketInfoId, false);

            // 修改准考证模板
            String templateId = dbAdmissionTicketInfo.getAdmissionTicketTemplateId();
            boolean isCustomTemplate = Objects.equals(TemplateTypeEnum.CUSTOM.getCode(), addOrUpdateAdmissionTicketInfoParam.getTemplateType());
            if (isUpdateTemplate(addOrUpdateAdmissionTicketInfoParam, dbAdmissionTicketInfo)) {
                // 删除旧的模板变量
                iAdmissionTicketTemplateVariableService.deleteByAdmissionId(dbAdmissionTicketInfo.getId(), currentUser);
                if (isCustomTemplate) {
                    // 从标准模板修改成了自定义模板

                    // 新建新的自定义模板
                    templateId = iAdmissionTicketTemplateService.createCustomTemplate(currentUser, deptId).getId();

                    // 更新准考证任务id到自定义模板中
                    iAdmissionTicketTemplateService.updateAdmissionTicketInfoId(dbAdmissionTicketInfo.getId(), templateId, currentUser);

                    // 初始化自定义模板变量
                    iAdmissionTicketTemplateVariableService.initCustomTemplateVariable(templateId, dbAdmissionTicketInfo.getId(), currentUser, deptId);
                } else {
                    boolean isStandardTemplate = TemplateTypeEnum.isStandard(dbAdmissionTicketInfo.getAdmissionTicketTemplateType());
                    if (isStandardTemplate) {
                        // 从标准模板修改成另一个标准模板 模板id直接用前端传过来的
                    } else {
                        // 从自定义模板修改成标准模板 模板id直接用前端传过来的
                        // 删除旧的自定义模板
                        iAdmissionTicketTemplateService.deleteTemplateByAdmissionInfoId(dbAdmissionTicketInfo.getId(), currentUser, deptId);
                    }
                    // 初始化新的模板变量
                    iAdmissionTicketTemplateVariableService.initStandardTemplateVariable(addOrUpdateAdmissionTicketInfoParam, dbAdmissionTicketInfo.getId(), currentUser, deptId);
                    templateId = addOrUpdateAdmissionTicketInfoParam.getTemplateType() + "";
                }
            }

            if (isUpdateExam(addOrUpdateAdmissionTicketInfoParam, dbAdmissionTicketInfo)) {
                // 删除旧考生数据 同步新考生数据
                iExamineeInfoService.deleteByAdmissionTicketId(addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId(), currentUser, deptId);

                // 同步考生数据数据
                syncData(addOrUpdateAdmissionTicketInfoParam, currentUser, addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId(), deptId);
            }

            // 更新准考证任务
            updateAdmissionTicketInfo(addOrUpdateAdmissionTicketInfoParam, currentUser, deptId, templateId);

            // 更新准考证任务创建完成
            updateCreateStatus(admissionTicketInfoId, true);
            admissionTicketInfoId = dbAdmissionTicketInfo.getId();
            log.info("[创建或者编辑准考证]编辑准考证任务完成: {}", admissionTicketInfoId);
        }
        ProgressManager.updateProgressCurrent(100L, admissionTicketInfoId);
        return admissionTicketInfoId;
    }

    /**
     * 更新准考证任务
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @param currentUser
     * @param deptId
     * @param templateId
     * @return
     */
    private void updateAdmissionTicketInfo(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam, User currentUser, String deptId, String templateId) {
        int defaultSubjectSetting = getSubjectSetting(addOrUpdateAdmissionTicketInfoParam);
        lambdaUpdate()
                .eq(BaseEntity::getDelFlag, false)
                .eq(BaseEntity::getId, addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId())
                .set(BaseEntity::getUpdateBy, currentUser.getId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .set(AdmissionTicketInfo::getAdmissionTicketName, addOrUpdateAdmissionTicketInfoParam.getName())
                .set(AdmissionTicketInfo::getAdmissionTicketDataSource, addOrUpdateAdmissionTicketInfoParam.getDataSourceCode())
                .set(AdmissionTicketInfo::getAdmissionTicketTemplateId, templateId)
                .set(AdmissionTicketInfo::getAdmissionTicketTemplateType, addOrUpdateAdmissionTicketInfoParam.getTemplateType() + "")
                .set(AdmissionTicketInfo::getSubjectSetting, defaultSubjectSetting)
                .set(AdmissionTicketInfo::getExamId, addOrUpdateAdmissionTicketInfoParam.getExamId())
                .set(AdmissionTicketInfo::getExamName, addOrUpdateAdmissionTicketInfoParam.getExamName())
                .update();
        log.info("更新准考证任务：{} {}", currentUser, addOrUpdateAdmissionTicketInfoParam);
    }

    /**
     * 获取科目设置
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @return
     */
    private int getSubjectSetting(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam) {
        int defaultSubjectSetting = MORE.getCode();
        if (Objects.equals(TicketDataSourceEnum.V_ROOM.getCode(), addOrUpdateAdmissionTicketInfoParam.getDataSourceCode())
                && Objects.equals(TemplateTypeEnum.CUSTOM.getCode(), addOrUpdateAdmissionTicketInfoParam.getTemplateType())) {
            defaultSubjectSetting = addOrUpdateAdmissionTicketInfoParam.getSubjectSetting();
        }
        return defaultSubjectSetting;
    }

    /**
     * 同步考生数据数据
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @param currentUser
     * @param admissionTicketInfoId
     * @param deptId
     */
    private void syncData(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam, User currentUser, String admissionTicketInfoId, String deptId) {
        log.info("[准考证][创建or编辑][数据同步]创建或者编辑准考证任务，开始同步学生数据: {}", addOrUpdateAdmissionTicketInfoParam);
        ProgressManager.updateProgressCurrent(10L, "准考证创建完成，开始同步学生数据");
        Integer dataSourceCode = addOrUpdateAdmissionTicketInfoParam.getDataSourceCode();
        if (Objects.equals(NOT_ROOM.getCode(), dataSourceCode)) {
            // 同步无考场数据
            iExamineeInfoService.syncKw(addOrUpdateAdmissionTicketInfoParam.getExamId(), currentUser, admissionTicketInfoId, deptId);
        } else {
            // 同步虚拟考场数据
            iExamineeInfoService.syncGov(addOrUpdateAdmissionTicketInfoParam.getExamId(), currentUser.getId(), admissionTicketInfoId);
        }

        // 准考证创建完成
        updateCreateStatus(admissionTicketInfoId, true);
    }

    /**
     * 更新准考证任务创建状态
     *
     * @param admissionTicketInfoId
     * @param createStatus
     */
    private void updateCreateStatus(String admissionTicketInfoId, boolean createStatus) {
        lambdaUpdate()
                .eq(BaseEntity::getId, admissionTicketInfoId)
                .set(AdmissionTicketInfo::getCreateStatus, createStatus)
                .update();
    }

    /**
     * 校验编辑准考证任务的参数
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @param currentUser
     * @param deptId
     * @param dbAdmissionTicketInfo
     */
    private void validationUpdateAdmissionTicketInfoParam(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam, User currentUser, String deptId, AdmissionTicketInfo dbAdmissionTicketInfo) {
        if (Objects.equals(true, dbAdmissionTicketInfo.getArchiveStatus())) {
            throw new KqException(ADMISSION_TICKET_INFO_UPDATE_TICKET_ARCHIVED_ERROR);
        }
        Boolean publishStatus = dbAdmissionTicketInfo.getPublishStatus();
        if (Objects.equals(true, publishStatus) || iExamineeInfoService.isGeneratedTicket(dbAdmissionTicketInfo.getId())) {
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_PUBLISH_ERROR);
        }
        validationVirtualPublishStatus(addOrUpdateAdmissionTicketInfoParam);
        validationName(addOrUpdateAdmissionTicketInfoParam.getName());
        String officeid = currentUser.getOfficeid();
        List<AdmissionTicketInfo> admissionTicketInfos = lambdaQuery()
                .eq(BaseEntity::getDelFlag, false)
                .eq(AdmissionTicketInfo::getAdmissionTicketName, addOrUpdateAdmissionTicketInfoParam.getName())
                .eq(AdmissionTicketInfo::getOfficeId, officeid)
                .eq(AdmissionTicketInfo::getDeptId, deptId)
                .ne(AdmissionTicketInfo::getId, addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId())
                .list();
        if (!admissionTicketInfos.isEmpty()) {
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_NAME_ERROR);
        }

        if (!Objects.equals(dbAdmissionTicketInfo.getAdmissionTicketDataSource(), addOrUpdateAdmissionTicketInfoParam.getDataSourceCode())) {
            // 修改数据来源 进行校验是否已经生成准考证
            if (iExamineeInfoService.isGeneratedTicket(addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId())) {
                throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_PUBLISH_ERROR);
            }
        }

        if (isUpdateTemplate(addOrUpdateAdmissionTicketInfoParam, dbAdmissionTicketInfo)) {
            // 修改模板 进行校验是否已经生成准考证
            if (iExamineeInfoService.isGeneratedTicket(addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId())) {
                throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_PUBLISH_ERROR);
            }
        }

        if (!Objects.equals(dbAdmissionTicketInfo.getSubjectSetting(), addOrUpdateAdmissionTicketInfoParam.getSubjectSetting())) {
            // 修改科目设置 进行校验是否已经生成准考证
            if (iExamineeInfoService.isGeneratedTicket(addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId())) {
                throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_PUBLISH_ERROR);
            }
        }

        if (isUpdateExam(addOrUpdateAdmissionTicketInfoParam, dbAdmissionTicketInfo)) {
            // 修改考试名称 进行校验是否已经生成准考证
            if (iExamineeInfoService.isGeneratedTicket(addOrUpdateAdmissionTicketInfoParam.getAdmissionTicketInfoId())) {
                throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_PUBLISH_ERROR);
            }
        }

        // 非报名导入的虚拟考场禁止使用自定义模板
        validationNoBmImport(addOrUpdateAdmissionTicketInfoParam);

        // 虚拟考场线下考试禁止使用多科目
        validationOnOffExam(addOrUpdateAdmissionTicketInfoParam);
    }

    /**
     * 是否修改考试
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @param dbAdmissionTicketInfo
     * @return
     */
    private boolean isUpdateExam(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam, AdmissionTicketInfo dbAdmissionTicketInfo) {
        return !Objects.equals(dbAdmissionTicketInfo.getExamId(), addOrUpdateAdmissionTicketInfoParam.getExamId());
    }

    /**
     * 是否修改了模板
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @param dbAdmissionTicketInfo
     * @return
     */
    private boolean isUpdateTemplate(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam, AdmissionTicketInfo dbAdmissionTicketInfo) {
        return !Objects.equals(dbAdmissionTicketInfo.getAdmissionTicketTemplateType(), addOrUpdateAdmissionTicketInfoParam.getTemplateType());
    }

    /**
     * 校验名称
     *
     * @param name
     */
    private void validationName(String name) {
        // 中英文加数字
        String rex = "^[\u4E00-\u9FFFa-zA-Z0-9]{1,50}$";
        if (StrUtil.isBlank(name) || !name.matches(rex)) {
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_NAME_ERROR);
        }
    }

    /**
     * 创建准考证任务
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @param currentUser
     * @param deptId
     * @param templateId
     * @return
     */
    private AdmissionTicketInfo createAdmissionTicketInfo(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam, User currentUser, String deptId, String templateId) {
        AdmissionTicketInfo admissionTicketInfo = new AdmissionTicketInfo();
        LocalDateTime now = LocalDateTime.now();
        admissionTicketInfo.setId(UUIDUtils.newSortUUID());
        admissionTicketInfo.setCreateBy(currentUser.getId());
        admissionTicketInfo.setCreateDate(now);
        admissionTicketInfo.setUpdateBy(currentUser.getId());
        admissionTicketInfo.setUpdateDate(now);

        admissionTicketInfo.setOfficeId(currentUser.getOfficeid());
        admissionTicketInfo.setDeptId(deptId);
        admissionTicketInfo.setAdmissionTicketTemplateId(templateId);
        admissionTicketInfo.setAdmissionTicketTemplateType(addOrUpdateAdmissionTicketInfoParam.getTemplateType());
        admissionTicketInfo.setAdmissionTicketName(addOrUpdateAdmissionTicketInfoParam.getName());
        admissionTicketInfo.setAdmissionTicketDataSource(addOrUpdateAdmissionTicketInfoParam.getDataSourceCode());
        admissionTicketInfo.setExamId(addOrUpdateAdmissionTicketInfoParam.getExamId());
        admissionTicketInfo.setExamName(addOrUpdateAdmissionTicketInfoParam.getExamName());
        admissionTicketInfo.setSubjectSetting(getSubjectSetting(addOrUpdateAdmissionTicketInfoParam));
        admissionTicketInfo.setArchiveStatus(false);
        admissionTicketInfo.setPublishStatus(false);
        admissionTicketInfo.setCreateStatus(false);

        save(admissionTicketInfo);
        log.info("[准考证][创建][新增准考证任务]新增准考证任务：{} {}", currentUser, admissionTicketInfo);
        return admissionTicketInfo;
    }

    /**
     * 校验新增准考证任务参数
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     * @param currentUser
     * @param deptId
     */
    private void validationAddAdmissionTicketInfoParam(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam, User currentUser, String deptId) {
        validationExist(addOrUpdateAdmissionTicketInfoParam.getExamId());
        validationName(addOrUpdateAdmissionTicketInfoParam.getName());
        validationVirtualPublishStatus(addOrUpdateAdmissionTicketInfoParam);
        String officeid = currentUser.getOfficeid();
        List<AdmissionTicketInfo> admissionTicketInfos = lambdaQuery()
                .eq(BaseEntity::getDelFlag, false)
                .eq(AdmissionTicketInfo::getAdmissionTicketName, addOrUpdateAdmissionTicketInfoParam.getName())
                .eq(AdmissionTicketInfo::getOfficeId, officeid)
                .eq(AdmissionTicketInfo::getDeptId, deptId)
                .list();
        if (!admissionTicketInfos.isEmpty()) {
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_NAME_ERROR);
        }
        // 非报名导入的虚拟考场禁止使用自定义模板
        validationNoBmImport(addOrUpdateAdmissionTicketInfoParam);

        // 虚拟考场线下考试禁止使用多科目
        validationOnOffExam(addOrUpdateAdmissionTicketInfoParam);
    }

    /**
     * 虚拟考场线下考试禁止使用多科目
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     */
    private void validationOnOffExam(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam) {
        String examId = addOrUpdateAdmissionTicketInfoParam.getExamId();
        Task task = iTaskService.getById(examId);
        boolean isOffline = Objects.nonNull(task) && Objects.equals(task.getExamType(), OFFLINE.getCode());
        boolean isMore = Objects.equals(addOrUpdateAdmissionTicketInfoParam.getSubjectSetting(), MORE.getCode());
        if (isOffline && isMore) {
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_OFF_LINE_MORE_SUBJECT_ERROR);
        }
    }

    /**
     * 校验虚拟考场发布状态 未发布不允许创建准考证
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     */
    private void validationVirtualPublishStatus(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam) {
        Integer dataSourceCode = addOrUpdateAdmissionTicketInfoParam.getDataSourceCode();
        if (Objects.equals(V_ROOM.getCode(), dataSourceCode)) {
            String examId = addOrUpdateAdmissionTicketInfoParam.getExamId();
            Task task = iTaskService.getById(examId);
            if (!Objects.equals(true, task.getPublish())) {
                // 虚拟考场未发布禁止创建
                throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_VIRTUAL_ROOM_NOT_PUBLISH_ERROR);
            }
        }
    }

    /**
     * 非报名导入的虚拟考场禁止使用自定义模板
     *
     * @param addOrUpdateAdmissionTicketInfoParam
     */
    private void validationNoBmImport(AddOrUpdateAdmissionTicketInfoParam addOrUpdateAdmissionTicketInfoParam) {
        Integer dataSourceCode = addOrUpdateAdmissionTicketInfoParam.getDataSourceCode();
        Integer templateType = addOrUpdateAdmissionTicketInfoParam.getTemplateType();
        if (Objects.equals(dataSourceCode, V_ROOM.getCode())) {
            String examId = addOrUpdateAdmissionTicketInfoParam.getExamId();
            if (!iTaskService.isBmImport(examId)
                    && Objects.equals(TemplateTypeEnum.CUSTOM.getCode(), templateType)) {
                throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_OOF_LINE_IMPORT_ERROR);
            }
        }
    }

    /**
     * 校验考试是否已经关联准考证
     *
     * @param examId
     */
    private void validationExist(String examId) {
        if (
                lambdaQuery()
                        .eq(BaseEntity::getDelFlag, false)
                        .eq(AdmissionTicketInfo::getExamId, examId)
                        .count() > 0
        ) {
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_EXIST_ERROR);
        }
    }

    /**
     * 设置归档状态
     *
     * @param updateAdmissionTicketInfoParam
     * @param currentUser
     * @param currentDeptInfo
     */
    @Override
    public void setArchiveStatus(UpdateAdmissionTicketInfoParam updateAdmissionTicketInfoParam, User currentUser, Organization currentDeptInfo) {
        lambdaUpdate()
                .eq(BaseEntity::getDelFlag, false)
                .eq(BaseEntity::getId, updateAdmissionTicketInfoParam.getId())
                .set(BaseEntity::getUpdateBy, currentUser.getId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .set(AdmissionTicketInfo::getArchiveStatus, updateAdmissionTicketInfoParam.getArchiveStatus())
                .update();
        log.info("设置归档状态：{} {}", currentUser, updateAdmissionTicketInfoParam);
    }

    /**
     * 根据id删除准考证任务
     *
     * @param id
     * @param currentUser
     * @param deptInfo
     */
    @Override
    public void delete(String id, User currentUser, Organization deptInfo) {
        AdmissionTicketInfo admissionTicketInfo = getById(id);
        if (Objects.isNull(admissionTicketInfo)) {
            return;
        }
        if (admissionTicketInfo.getPublishStatus() || iExamineeInfoService.isGeneratedTicket(id)) {
            throw new KqException(ADMISSION_TICKET_INFO_PUBLISH_DELETE_FAIL);
        }

        LocalDateTime now = LocalDateTime.now();
        boolean update = lambdaUpdate()
                .eq(BaseEntity::getId, id)
                .set(BaseEntity::getDelFlag, true)
                .set(BaseEntity::getUpdateBy, currentUser.getId())
                .set(BaseEntity::getUpdateDate, now)
                .update();
        if (update) {
            iAdmissionTicketTemplateService.deleteTemplateByAdmissionInfoId(id, currentUser, deptInfo.getId());
            iExamineeInfoService.deleteByAdmissionTicketId(id, currentUser, deptInfo.getId());

            // 无考场删除考试配置信息
            if (Objects.equals(NOT_ROOM.getCode(), admissionTicketInfo.getAdmissionTicketDataSource())) {
                removeCollect(admissionTicketInfo.getExamId());
            }
            log.info("{} {}删除了准考证任务{}", currentUser.getId(), now, id);
        }
    }

    /**
     * 删除考生采集配置
     *
     * @param projectId
     */
    @Override
    public void removeCollect(String projectId) {
        iProjectCollectInfoService.removeByProjectId(projectId);
        iProjectCollectInfoOptionService.removeByProjectId(projectId);
        iProjectImageStandardService.removeByProjectId(projectId);
    }

    /**
     * 根据条件查询准考证任务
     *
     * @param listByConditionDTO
     * @param user
     * @param deptIds
     * @return
     */
    @Override
    public IPage<AdmissionTicketInfoVO> pageByCondition(PageByCondition listByConditionDTO, User user, Set<String> deptIds) {
        LambdaQueryWrapper<AdmissionTicketInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BaseEntity::getDelFlag, false);
        queryWrapper.eq(AdmissionTicketInfo::getOfficeId, user.getOfficeid());
        queryWrapper.in(AdmissionTicketInfo::getDeptId, deptIds);
        queryWrapper.orderByDesc(BaseEntity::getCreateDate);

        String name = listByConditionDTO.getName();
        Boolean archiveStatus = listByConditionDTO.getArchiveStatus();
        if (StrUtil.isNotBlank(name)) {
            queryWrapper.like(AdmissionTicketInfo::getAdmissionTicketName, name);
        }
        if (Objects.nonNull(archiveStatus)) {
            queryWrapper.eq(AdmissionTicketInfo::getArchiveStatus, archiveStatus);
        }

        IPage<AdmissionTicketInfo> page = this.page(new Page<>(listByConditionDTO.getCurrent(), listByConditionDTO.getSize()), queryWrapper);
        IPage<AdmissionTicketInfoVO> admissionTicketInfoVOPage = page.convert(admissionTicketInfo -> {
            AdmissionTicketInfoVO admissionTicketInfoVO = new AdmissionTicketInfoVO(admissionTicketInfo);
            countTicketNum(admissionTicketInfoVO);
            admissionTicketInfoVO.setIsGeneratedTicket(iExamineeInfoService.isGeneratedTicket(admissionTicketInfoVO.getId()));
            return admissionTicketInfoVO;
        });

        return admissionTicketInfoVOPage;
    }

    /**
     * 统计准考证生成数量
     *
     * @param admissionTicketInfoVO
     */
    private void countTicketNum(AdmissionTicketInfoVO admissionTicketInfoVO) {
        String admissionId = admissionTicketInfoVO.getId();
        Integer subjectSetting = admissionTicketInfoVO.getSubjectSetting();
        // 虚拟考场 自定义模板 单科目生成需要单独判断 其他默认都是多科目生成
        if (Objects.equals(subjectSetting, TicketSubjectSettingEnum.SINGLE.getCode())) {
            Integer totalCount = iExamineeTimesService.timesCount(admissionId);
            Integer createdCount = iExamineeTimesService.timesGeneratedCount(admissionTicketInfoVO.getId());
            admissionTicketInfoVO.setTotalNum(totalCount);
            admissionTicketInfoVO.setCreatedNum(createdCount);
            admissionTicketInfoVO.setSurplusNum(totalCount - createdCount);
        } else {
            Integer totalCount = iExamineeInfoService.examineeCount(admissionTicketInfoVO.getId());
            Integer createdCount = iExamineeInfoService.ticketGeneratedCount(admissionTicketInfoVO.getId());
            admissionTicketInfoVO.setTotalNum(totalCount);
            admissionTicketInfoVO.setCreatedNum(createdCount);
            admissionTicketInfoVO.setSurplusNum(totalCount - createdCount);
        }
    }

    @Override
    public AdmissionTicketInfo getByExamId(String examId) {
        return lambdaQuery()
                .eq(AdmissionTicketInfo::getExamId, examId)
                .eq(AdmissionTicketInfo::getDelFlag, false)
                .last("limit 1").one();
    }

    /**
     * 检测准考证任务是否存在，存在则返回，不存在则报错
     *
     * @param examId
     * @return
     */
    @Override
    public AdmissionTicketInfo checkAndGetAdmissionTicketInfo(String examId) {
        AdmissionTicketInfo admissionTicketInfo = lambdaQuery()
                .eq(AdmissionTicketInfo::getExamId, examId)
                .eq(AdmissionTicketInfo::getDelFlag, false)
                .orderByDesc(BaseEntity::getCreateDate)
                .last("limit 1")
                .one();
        if (Objects.isNull(admissionTicketInfo)) {
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_NOT_EXIST_ERROR);
        }
        return admissionTicketInfo;
    }

    @SuppressWarnings("all")
    @SneakyThrows
    @Override
    public void exportAdmissionTicketExcel(ExamineeInfoExportParam examineeInfoExportParam) {
        AdmissionTicketInfo admissionTicketInfo = this.getById(examineeInfoExportParam.getAdmissionTicketInfoId());
        if (admissionTicketInfo == null) {
            BusinessLogicException.throwException(43340502, "无效的准考证");
        }

        List<ExportExamineesInfoDTO> exportAdmissionTicketExcelList = Lists.newArrayList();
        switch (TicketSubjectSettingEnum.getByCode(admissionTicketInfo.getSubjectSetting())) {
            case MORE:
                exportAdmissionTicketExcelList = exportAdmissionTicketExcelMulti(examineeInfoExportParam, admissionTicketInfo);
                break;
            default:
                exportAdmissionTicketExcelList = exportAdmissionTicketExcelSingle(examineeInfoExportParam, admissionTicketInfo);
                break;
        }
        ProgressManager.updateProgressCurrent(50L, "Excel文件数据准备完成");

        // 集合总数据数
        int totalSize = CollUtil.size(exportAdmissionTicketExcelList);
        // 文件名称
        String lastFileName = admissionTicketInfo.getAdmissionTicketName() + ".xlsx";
        File tempFile = FileUtil.createTempFile(lastFileName, Boolean.TRUE);
        tempFile.deleteOnExit();
        EasyExcel.write(tempFile, ExportExamineesInfoDTO.class)
                .registerWriteHandler(new AbstractRowWriteHandler() {
                    @Override
                    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
                        super.afterRowDispose(writeSheetHolder, writeTableHolder, row, relativeRowIndex, isHead);
                        String result = StrUtil.format("数据正在写入Excel文件，共计{}条数据，正在写入第{}条数据", totalSize, relativeRowIndex);
                        log.debug("# {}", result);
                        ProgressManager.stepProgressCurrent(relativeRowIndex, totalSize, 40L, 90L, result);
                    }
                })
                .sheet(admissionTicketInfo.getAdmissionTicketName()).doWrite(exportAdmissionTicketExcelList);

        ProgressManager.updateProgressCurrent(90L, "文件开始上传华为云");
        String path = "admission/ticket/" + lastFileName;
        String fileUrl = cloudObsService.updateFile(path, tempFile);
        log.debug("# excel文件上传华为云成功：{}，{}", lastFileName, fileUrl);
        ProgressManager.updateProgressCurrent(99L, "文件上传华为云成功");
        tempFile.delete();
        ProgressManager.updateProgressCurrent(100L, fileUrl);
    }

    /**
     * 导出考生准考证信息单科目维度
     *
     * @param examineeInfoExportParam
     * @param admissionTicketInfo
     * @return
     */
    private List<ExportExamineesInfoDTO> exportAdmissionTicketExcelSingle(ExamineeInfoExportParam examineeInfoExportParam, AdmissionTicketInfo admissionTicketInfo) {
        List<ExamineeInfoPageVO> examineeInfoPageVOS = iExamineeInfoService.exportAdmissionTicketExcelSingle(examineeInfoExportParam);
        ProgressManager.updateProgressCurrent(20L, "考生准考证信息数据准备完成");

        int totalSize = CollUtil.size(examineeInfoPageVOS);
        AtomicInteger atomicInteger = new AtomicInteger();
        List<ExportExamineesInfoDTO> exportExamineesInfoDTOS = examineeInfoPageVOS.stream()
                .peek(data -> {
                    String result = StrUtil.format("正在准备考生{}的数据", data.getExamineeName());
                    ProgressManager.stepProgressCurrent(atomicInteger.getAndIncrement(), totalSize, 30L, 50L, result);
                })
                // 转换响应实体类
                .map(examineeInfo -> this.convertExportExamineesInfoDTO(examineeInfo, admissionTicketInfo, null))
                .collect(Collectors.toList());
        return exportExamineesInfoDTOS;
    }

    /**
     * 导出考生准考证信息多科目维度
     *
     * @param examineeInfoExportParam
     * @param admissionTicketInfo
     * @return
     */
    private List<ExportExamineesInfoDTO> exportAdmissionTicketExcelMulti(ExamineeInfoExportParam examineeInfoExportParam, AdmissionTicketInfo admissionTicketInfo) {
        List<ExamineeInfo> examineeInfos = iExamineeInfoService.lambdaQuery()
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .eq(ExamineeInfo::getAdmissionTicketInfoId, examineeInfoExportParam.getAdmissionTicketInfoId())
                .and(StrUtil.isNotBlank(examineeInfoExportParam.getSearchWord()),
                        wrapper -> wrapper.like(ExamineeInfo::getExamineeName, examineeInfoExportParam.getSearchWord())
                                .or()
                                .like(ExamineeInfo::getIdentificationNo, examineeInfoExportParam.getSearchWord()))
                .eq(ObjectUtil.isNotEmpty(examineeInfoExportParam.getGenerateStatus()), ExamineeInfo::getGenerateStatus, examineeInfoExportParam.getGenerateStatus())
                .list();
        ProgressManager.updateProgressCurrent(20L, "考生准考证信息数据准备完成");

        int totalSize = CollUtil.size(examineeInfos);
        AtomicInteger atomicInteger = new AtomicInteger();
        List<ExportExamineesInfoDTO> exportAdmissionTicketExcelMulti = examineeInfos.stream()
                .peek(data -> {
                    String result = StrUtil.format("正在准备考生{}的数据", data.getExamineeName());
                    ProgressManager.stepProgressCurrent(atomicInteger.getAndIncrement(), totalSize, 30L, 50L, result);
                })
                // 转换响应实体类
                .map(examineeInfo -> this.convertExportExamineesInfoDTO(examineeInfo, admissionTicketInfo, examineeInfoPageVO -> {
                    // 获取考生批次信息
                    List<ExamineeTimes> examineeTimes = iExamineeTimesService.getByExamineeId(examineeInfo.getId());
                    // 科目
                    String subjectNames = examineeTimes.stream()
                            .map(ExamineeTimes::getSubjectName)
                            .filter(StrUtil::isNotBlank)
                            .collect(Collectors.joining(StrPool.SLASH));
                    examineeInfoPageVO.setSubjectName(subjectNames);
                    // 准考证下载地址
                    examineeTimes.stream()
                            .map(ExamineeTimes::getAdmissionTicketPdfUrl)
                            .filter(StrUtil::isNotBlank)
                            .findAny()
                            .ifPresent(examineeInfoPageVO::setAdmissionTicketFileUrls);
                })).collect(Collectors.toList());
        return exportAdmissionTicketExcelMulti;
    }

    /**
     * 转换考生导出信息DTO
     *
     * @param examineeInfo
     * @param admissionTicketInfo
     * @return
     */
    private ExportExamineesInfoDTO convertExportExamineesInfoDTO(ExamineeInfo examineeInfo, AdmissionTicketInfo admissionTicketInfo, Consumer<ExportExamineesInfoDTO> consumer) {
        ExportExamineesInfoDTO exportExamineesInfoDTO = BeanUtil.toBean(examineeInfo, ExportExamineesInfoDTO.class);
        // 考试时间
        String examineStartTime = DateUtil.format(admissionTicketInfo.getBeginDate(), DatePattern.NORM_DATE_PATTERN);
        String examineEndTime = DateUtil.format(admissionTicketInfo.getEndDate(), DatePattern.NORM_DATE_PATTERN);
        String mergeExamineDate = StrUtil.format("{}至{}",
                Optional.ofNullable(examineStartTime)
                        .orElse(StrPool.DASHED),
                Optional.ofNullable(examineEndTime)
                        .orElse(StrPool.DASHED));
        exportExamineesInfoDTO.setExamineeTime(mergeExamineDate);
        // 准考证号
        Optional.ofNullable(exportExamineesInfoDTO.getAdmissionSerialNumber())
                .filter(StrUtil::isNotBlank)
                .map(data -> data.replace(StrPool.COMMA, StrPool.SLASH))
                .ifPresent(exportExamineesInfoDTO::setAdmissionSerialNumber);
        // 性别
        String sexDesc = AdmissionSexEnum.convert(examineeInfo.getExamineeSex());
        exportExamineesInfoDTO.setExamineeSexDesc(sexDesc);
        // 准考证生成时间
        Optional.ofNullable(examineeInfo.getGenerateData())
                .map(data -> DateUtil.format(data, DatePattern.NORM_DATE_PATTERN))
                .ifPresent(exportExamineesInfoDTO::setGenerateDataText);
        // 生成状态
        String generateDesc = GenerateStatusEnum.getDescByType(examineeInfo.getGenerateStatus());
        exportExamineesInfoDTO.setGenerateStatusDesc(generateDesc);
        // 证件类型
        String identityTypeDesc = IdentityTypeEnum.getDescByType(examineeInfo.getIdentificationType());
        exportExamineesInfoDTO.setIdentificationTypeDesc(identityTypeDesc);
        if (consumer != null) {
            consumer.accept(exportExamineesInfoDTO);
        }
        return exportExamineesInfoDTO;
    }

    @Override
    public List<AdmissionTicketInfo> queryTicketListByOfficeId(String officeId, String deptId) {
        return lambdaQuery()
                .eq(AdmissionTicketInfo::getOfficeId, officeId)
                .eq(AdmissionTicketInfo::getDelFlag, false)
                .last("limit 1000").list();
    }

    @Override
    public void batchDownloadAdmissionTicket(String admissionTicketInfoId, String userId) {
        // 获取最新的准考证考生更新时间，这里只拿更新时间，条件只判断最的更新时间，不判断任何状态值
        Optional<LocalDateTime> updateDateOptional = iExamineeTimesService.lambdaQuery()
                .select(BaseEntity::getUpdateDate)
                .eq(ExamineeTimes::getAdmissionTicketInfoId, admissionTicketInfoId)
                .orderByDesc(BaseEntity::getUpdateDate)
                .last("LIMIT 1")
                .oneOpt()
                .map(BaseEntity::getUpdateDate);
        if (updateDateOptional.isPresent()) {
            ProgressManager.updateProgress(1);

            LocalDateTime updateDate = updateDateOptional.get();
            // 获取文件下载记录下载中，则直接告诉客户下载
            FileRecodeParam fileRecodeParam = new FileRecodeParam();
            fileRecodeParam.setProjectId(admissionTicketInfoId);
            fileRecodeParam.setSystemType(SystemEnum.KWGOV.getSystemType());
            fileRecodeParam.setFileType(FileTypeEnum.KW_GOV_BATCH_DOWNLOAD_ADMISSION_TICKET.getFileType());
            CloudeFileProjectRecord cloudeFileProjectRecord = RemoteUtil.getOrThrows(() -> cloudFileClient.queryLastFileRecode(fileRecodeParam));
            if (cloudeFileProjectRecord != null) {
                ProgressManager.updateProgress(2);

                if (FileStatusEnum.SUCCESS.getStatus().equals(cloudeFileProjectRecord.getStatus())
                        && StrUtil.isNotBlank(cloudeFileProjectRecord.getFileUrl())) {
                    ProgressManager.updateProgress(3);

                    // 获取下载记录的创建时间
                    LocalDateTime recordCreateDate = cloudeFileProjectRecord.getCreateDate();
                    // 获取试卷的最新的时间
                    if (recordCreateDate.isAfter(updateDate)) {
                        ProgressManager.updateProgress(4);

                        log.debug("# 批量下载考生准考证已存在生成成功文件，直接返回：{}", admissionTicketInfoId);
                        ProgressManager.finish(cloudeFileProjectRecord.getFileUrl());
                        return;
                    }
                }
            }
        }

        PushTaskParam pushTaskParam = new PushTaskParam();
        pushTaskParam.setProjectId(admissionTicketInfoId);
        pushTaskParam.setSystemType(SystemEnum.KWGOV.getSystemType());
        pushTaskParam.setOperateType(OperateTypeEnum.FILE_DOWNLOAD.getOperateType());
        pushTaskParam.setFileType(FileTypeEnum.KW_GOV_BATCH_DOWNLOAD_ADMISSION_TICKET.getFileType());
        pushTaskParam.setFileKey(JobKeyEnum.KW_GOV_BATCH_DOWNLOAD_ADMISSION_TICKET_JOB.getKey());
        pushTaskParam.setExecuteImmediately(Boolean.TRUE);
        pushTaskParam.setOfficeId(loginService.getOfficeId());
        pushTaskParam.setDeptId(loginService.getDeptId());
        pushTaskParam.setUserId(loginService.getUserId());
        Map<String, Object> progressKey = MapBuilder.<String, Object>create()
                .put(ProgressConstant.PROGRESS_KEY, ProgressHolder.get())
                .build();
        pushTaskParam.setDynamicAttr(progressKey);
        pushTaskParam.setUserId(userId);
        ProgressManager.getProgressId().ifPresent(pushTaskParam::setProgressId);
        ApiResponse<Boolean> response = cloudFileClient.pushTask(pushTaskParam);
        // 任务处理失败
        if (RemoteUtil.isFail(response) || !response.getData()) {
            BusinessLogicException.throwException(43341104, "批量下载准考证任务处理失败");
        }
    }

    @Override
    public Boolean setCertificateStatus(String infoId, Boolean status) {
        AdmissionTicketInfo admissionTicketInfo = Optional.ofNullable(this.getById(infoId))
                .orElseThrow(() -> BusinessRequestException.getException(43340502, "无效的准考证"));

        AdmissionTicketInfo updatePublishStatus = new AdmissionTicketInfo();
        updatePublishStatus.setPublishStatus(status)
                .setId(infoId)
                .setUpdateBy(loginService.getUserId())
                .setUpdateDate(LocalDateTime.now());
        return this.updateById(updatePublishStatus);
    }

    /**
     * 查询数据来源
     *
     * @param admissionTicketInfoId
     * @return
     */
    @Override
    public List<AdmissionDataSourceVO> listDataSource(String admissionTicketInfoId) {
        List<AdmissionDataSourceVO> admissionDataSourceVOS = new LinkedList<>();
        if (StrUtil.isBlank(admissionTicketInfoId)) {
            // 新增
            for (TicketDataSourceEnum value : TicketDataSourceEnum.values()) {
                AdmissionDataSourceVO admissionDataSourceVO = new AdmissionDataSourceVO();
                admissionDataSourceVO.setCode(value.getCode());
                admissionDataSourceVO.setName(value.getText());
                admissionDataSourceVO.setIsCurrentUse(false);
                admissionDataSourceVOS.add(admissionDataSourceVO);
            }
        } else {
            // 编辑
            AdmissionTicketInfo admissionTicketInfo = getById(admissionTicketInfoId);
            for (TicketDataSourceEnum value : TicketDataSourceEnum.values()) {
                AdmissionDataSourceVO admissionDataSourceVO = new AdmissionDataSourceVO();
                admissionDataSourceVO.setCode(value.getCode());
                admissionDataSourceVO.setName(value.getText());
                admissionDataSourceVO.setIsCurrentUse(Objects.equals(admissionTicketInfo.getAdmissionTicketDataSource(), value.getCode()));
                admissionDataSourceVOS.add(admissionDataSourceVO);
            }
        }
        return admissionDataSourceVOS;
    }

    /**
     * 新增和编辑时查询考试名称
     *
     * @param admissionTicketInfoId
     * @param dataSourceCode
     * @param currentUser
     * @param deptIds
     * @return
     */
    @Override
    public List<AdmissionKsNameVO> listExam(String admissionTicketInfoId, Integer dataSourceCode, User currentUser, Set<String> deptIds) {
        // 已经关联的准考证的考试
        Set<String> bindExamIds = listOfficeAdmissionInfo(currentUser, deptIds)
                .stream().map(AdmissionTicketInfo::getExamId).collect(Collectors.toSet());

        List<AdmissionKsNameVO> admissionKsNameVOS = new LinkedList<>();
        boolean isUpdate = StrUtil.isNotBlank(admissionTicketInfoId);
        String updateExamId = null;

        // 1.更新时需要排除除了自己之外其他已经关联的考试 2.新增时需要排除所有已经关联的考试
        Set<String> excludeExamIds = bindExamIds;
        if (isUpdate) {
            updateExamId = getById(admissionTicketInfoId).getExamId();
            excludeExamIds.remove(updateExamId);
        }
        if (Objects.equals(NOT_ROOM.getCode(), dataSourceCode)) {
            // 关联无考场
            List<KwExamVO> kwExamVOS = RemoteUtil.getOrThrows(() -> ksFallbackFactoryClient.listDeptIdsExam(currentUser.getOfficeid(), deptIds));
            Set<String> kwExamIds = kwExamVOS.stream().map(KwExamVO::getId).collect(Collectors.toSet());
            if (isUpdate && !kwExamIds.contains(updateExamId)) {
                // 更新时如果之前关联的考试已经过期，此时查询不出来，需要单独查询，这里使用CollUtil.getFirst是考中没有根据机构、部门ID去进行数据查询
                KwExamVO updateExam = RemoteUtil.getOrThrows(ksFallbackFactoryClient.exam(currentUser.getOfficeid(), CollUtil.getFirst(deptIds), updateExamId));
                kwExamVOS.add(updateExam);
            }
            kwExamVOS = kwExamVOS.stream().filter(kwExamVO -> !excludeExamIds.contains(kwExamVO.getId())).collect(Collectors.toList());
            for (KwExamVO kwExamVO : kwExamVOS) {
                AdmissionKsNameVO admissionKsNameVO = new AdmissionKsNameVO();
                admissionKsNameVO.setId(kwExamVO.getId());
                admissionKsNameVO.setName(kwExamVO.getExamName());
                admissionKsNameVO.setIsCurrentUse(Objects.equals(updateExamId, kwExamVO.getId()));
                admissionKsNameVOS.add(admissionKsNameVO);
            }
        } else {
            // 关联虚拟考场 查询已发布虚拟考场数据
            List<Task> tasks = listValidPublishTask(currentUser, deptIds);
            Set<String> taskIds = tasks.stream().map(Task::getId).collect(Collectors.toSet());
            if (isUpdate && !taskIds.contains(updateExamId)) {
                // 更新时如果之前关联的考试已经过期，此时查询不出来，需要单独查询
                Task updateTask = iTaskService.getById(updateExamId);
                tasks.add(updateTask);
            }

            tasks = tasks.stream().filter(task -> !excludeExamIds.contains(task.getId())).collect(Collectors.toList());
            for (Task task : tasks) {
                AdmissionKsNameVO admissionKsNameVO = new AdmissionKsNameVO();
                admissionKsNameVO.setId(task.getId());
                admissionKsNameVO.setName(task.getName());
                admissionKsNameVO.setIsCurrentUse(Objects.equals(updateExamId, task.getId()));
                admissionKsNameVOS.add(admissionKsNameVO);
            }
        }
        return admissionKsNameVOS;
    }

    /**
     * 查询已经发布，且有效（进行中和未开始的）的虚拟考场
     *
     * @param currentUser
     * @param deptIds
     * @return
     */
    private List<Task> listValidPublishTask(User currentUser, Set<String> deptIds) {
        return iTaskService.lambdaQuery()
                .gt(Task::getEndDate, LocalDateTime.now())
                .eq(Task::getPublish, 1)
                .eq(Task::getDelFlag, false)
                .eq(Task::getOfficeId, currentUser.getOfficeid())
                .in(Task::getDeptId, deptIds)
                .orderByDesc(BaseEntity::getCreateDate)
                .list();
    }

    /**
     * 查询指定机构的准考证任务
     *
     * @param currentUser
     * @param deptIds
     * @return
     */
    private List<AdmissionTicketInfo> listOfficeAdmissionInfo(User currentUser, Set<String> deptIds) {
        return lambdaQuery()
                .eq(BaseEntity::getDelFlag, false)
                .eq(AdmissionTicketInfo::getOfficeId, currentUser.getOfficeid())
                .in(AdmissionTicketInfo::getDeptId, deptIds)
                .list();
    }

    /**
     * 通过id查询准考证vo 查询不到报错
     *
     * @param admissionTicketInfoId
     * @return
     */
    @Override
    public AdmissionTicketInfoVO getVOByAdmissionIdOrThrow(String admissionTicketInfoId) {
        AdmissionTicketInfo admissionTicketInfo = getByAdmissionIdOrThrow(admissionTicketInfoId);
        AdmissionTicketInfoVO admissionTicketInfoVO = new AdmissionTicketInfoVO(admissionTicketInfo);
        admissionTicketInfoVO.setIsGeneratedTicket(iExamineeInfoService.isGeneratedTicket(admissionTicketInfoId));
        admissionTicketInfoVO.setExamAgreement(iAdmissionTicketTemplateVariableService.getExamAgreement(admissionTicketInfoId));
        admissionTicketInfoVO.setIsSaveCustomTemplate(iAdmissionTicketTemplateService.isSaveTemplate(admissionTicketInfo));
        return admissionTicketInfoVO;
    }

    /**
     * 通过id查询准考证 查询不到报错
     *
     * @param admissionTicketInfoId
     * @return
     */
    @Override
    public AdmissionTicketInfo getByAdmissionIdOrThrow(String admissionTicketInfoId) {
        AdmissionTicketInfo admissionTicketInfo = lambdaQuery()
                .eq(BaseEntity::getDelFlag, false)
                .eq(BaseEntity::getId, admissionTicketInfoId)
                .orderByDesc(BaseEntity::getCreateDate)
                .last("limit 1").one();
        if (Objects.isNull(admissionTicketInfo)) {
            throw new KqException(ADMISSION_TICKET_INFO_CREATE_TICKET_NOT_EXIST_ERROR);
        }
        return admissionTicketInfo;
    }

    @Override
    public void batchGenerateAdmissionTicket(String admissionTicketInfoId, String userId) {
        PushTaskParam pushTaskParam = new PushTaskParam();
        pushTaskParam.setProjectId(admissionTicketInfoId);
        pushTaskParam.setSystemType(SystemEnum.KWGOV.getSystemType());
        pushTaskParam.setOperateType(OperateTypeEnum.FILE_GENERATE.getOperateType());
        pushTaskParam.setFileType(FileTypeEnum.KW_GOV_BATCH_GENERATE_ADMISSION_TICKET.getFileType());
        pushTaskParam.setFileKey(JobKeyEnum.KW_GOV_BATCH_GENERATE_ADMISSION_TICKET_JOB.getKey());
        pushTaskParam.setExecuteImmediately(Boolean.TRUE);
        pushTaskParam.setOfficeId(loginService.getOfficeId());
        pushTaskParam.setDeptId(loginService.getDeptId());
        pushTaskParam.setUserId(loginService.getUserId());
        Map<String, Object> progressKey = MapBuilder.<String, Object>create()
                .put(ProgressConstant.PROGRESS_KEY, ProgressHolder.get())
                .build();
        pushTaskParam.setDynamicAttr(progressKey);
        pushTaskParam.setUserId(userId);
        ProgressManager.getProgressId().ifPresent(pushTaskParam::setProgressId);
        ApiResponse<Boolean> response = cloudFileClient.pushTask(pushTaskParam);
        // 任务处理失败
        log.debug("isFail:{}, response data:{}", RemoteUtil.isFail(response), response.getData());
        if (RemoteUtil.isFail(response) || !response.getData()) {
            BusinessLogicException.throwException(43341104, "批量生成准考证任务处理失败");
        }
    }

    /**
     * 更新考试时间
     *
     * @param admissionTaskId
     * @param beginDate
     * @param endDate
     */
    @Override
    public void updateBeginAndEndDate(String admissionTaskId, LocalDateTime beginDate, LocalDateTime endDate) {
        lambdaUpdate()
                .eq(BaseEntity::getDelFlag, false)
                .eq(BaseEntity::getId, admissionTaskId)
                .set(AdmissionTicketInfo::getBeginDate, beginDate)
                .set(AdmissionTicketInfo::getEndDate, endDate)
                .eq(BaseEntity::getId, admissionTaskId)
                .update();
    }

    /**
     * 通过虚拟考场id或者无考场id查询准考证
     *
     * @param examId
     * @return
     */
    @Override
    public AdmissionTicketInfoVO getVOByExamId(String examId) {
        AdmissionTicketInfo admissionTicketInfo = getByExamId(examId);
        if (Objects.isNull(admissionTicketInfo)) {
            return null;
        }
        return new AdmissionTicketInfoVO(admissionTicketInfo);
    }

    /**
     * 更新考试信息
     *
     * @param examId
     * @param loginUser
     */
    @Override
    public void updateKwExam(String examId, User loginUser) {
        log.info("更新考试信息：{} {}", examId, loginUser);
        Task task = iTaskService.getById(examId);
        if (Objects.nonNull(task)) {
            // 虚拟考场
            lambdaUpdate()
                    .eq(AdmissionTicketInfo::getDelFlag, false)
                    .eq(AdmissionTicketInfo::getExamId, examId)
                    .set(AdmissionTicketInfo::getUpdateDate, LocalDateTime.now())
                    .set(AdmissionTicketInfo::getUpdateBy, loginUser.getId())
                    .set(AdmissionTicketInfo::getExamName, task.getName())
                    .set(AdmissionTicketInfo::getBeginDate, task.getBeginDate())
                    .set(AdmissionTicketInfo::getEndDate, task.getEndDate())
                    .update();
        } else {
            // 无考场
            KwExamVO kwExamVO = ksFallbackFactoryClient.exam(loginUser.getOfficeid(), loginUser.getOrganizationid(), examId).getData();
            lambdaUpdate()
                    .eq(AdmissionTicketInfo::getDelFlag, false)
                    .eq(AdmissionTicketInfo::getExamId, examId)
                    .set(AdmissionTicketInfo::getUpdateDate, LocalDateTime.now())
                    .set(AdmissionTicketInfo::getUpdateBy, loginUser.getId())
                    .set(AdmissionTicketInfo::getExamName, kwExamVO.getExamName())
                    .set(AdmissionTicketInfo::getBeginDate, kwExamVO.getBeginDate())
                    .set(AdmissionTicketInfo::getEndDate, kwExamVO.getEndDate())
                    .update();
        }
    }


    /**
     * 更新无考场考试信息
     *
     * @param kwExamVO
     * @param userId
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateKwExam(KwExamVO kwExamVO, String userId) {
        String examId = kwExamVO.getId();
        AdmissionTicketInfo admissionTicketInfo = iAdmissionTicketInfoService.getByExamId(examId);
        if (admissionTicketInfo == null) {
            log.debug("[准考证][无考场][考生数据同步][考试更新]未查询到当前无考场存在绑定准考证任务，跳过更新无考场考试信息");
            return;
        }

        lambdaUpdate()
                .eq(AdmissionTicketInfo::getDelFlag, false)
                .eq(AdmissionTicketInfo::getExamId, kwExamVO.getId())
                .set(AdmissionTicketInfo::getExamName, kwExamVO.getExamName())
                .set(AdmissionTicketInfo::getBeginDate, kwExamVO.getBeginDate())
                .set(AdmissionTicketInfo::getEndDate, kwExamVO.getEndDate())
                .set(AdmissionTicketInfo::getUpdateBy, userId)
                .set(AdmissionTicketInfo::getUpdateDate, LocalDateTime.now())
                .update();
        iExamineeTimesService.updateKwExam(kwExamVO, userId);
        iAdmissionTicketTemplateVariableService.updateKwExamAgreement(kwExamVO, userId);
        iAdmissionTicketTemplateVariableService.updateKwExamName(kwExamVO, userId);
    }
}
